/** 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter;

import it.free.i.virtual.machine.hal.devicealias.laboratory.SimulationLaboratoryGeneralException;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Observable;

import org.apache.log4j.Logger;

/**
 * Singleton to group up all the parameter instance used by the simulation package.
 * This add commodities to manage dynamic parameters' common tasks
 * @author stefano
 */
public class DynamicParameterRegistry extends Hashtable<String, DynamicParameter> {

	private static Logger logger = null;
	private static DynamicParameterRegistry DYNAMIC_PARAMETER_REGISTRY_INSTANCE = null;
	private static final long serialVersionUID = 6485503523722912968L;
    private static final String MY_DYNAMIC_PARAMETERS_THREAD_GROUP_NAME = "thread.group.dynamicparameters";
	private ThreadGroup threadGroup = null;
	
	static{
		logger = Logger.getLogger(DynamicParameterRegistry.class);
	}
	
	/**
	 * Unaccessible 
	 */
	private DynamicParameterRegistry() {
		threadGroup = new ThreadGroup(MY_DYNAMIC_PARAMETERS_THREAD_GROUP_NAME);
	}
	
	/**
	 * Singldeton to get the only common instance of the registry
	 * @return the common instance of this registry
	 */
	public static final DynamicParameterRegistry getInstance(){
		
		if(DYNAMIC_PARAMETER_REGISTRY_INSTANCE == null){
		   synchronized(DynamicParameterRegistry.class){
			   if(DYNAMIC_PARAMETER_REGISTRY_INSTANCE == null){
				   DYNAMIC_PARAMETER_REGISTRY_INSTANCE = new DynamicParameterRegistry();
			   }
		   }
		}
		
		return DYNAMIC_PARAMETER_REGISTRY_INSTANCE;
	}
	
	/**
	 * Get a dynamic parameter by name
	 * @param parameterName is the name of the parameter to get
	 * @return the associated dynamic parameter implementation
	 */
	public DynamicParameter getParameter(String parameterName){
		
		DynamicParameter dynamicParameter = null;
		
		if(super.containsKey(parameterName)){
		   dynamicParameter = super.get(parameterName);
		}
		
		return dynamicParameter;
	}
	
	/**
	 * Get a dynamic parameter by name
	 * @param parameterName is the name of the parameter to set
	 * @param dynamicParameter is the parameter implementation associated with that name 
	 * @throws SimulationLaboratoryGeneralException 
	 */
	public synchronized void setParameter(String parameterName, DynamicParameter dynamicParameter) throws SimulationLaboratoryGeneralException{
		
		if(super.containsKey(parameterName)){
		   throw (new SimulationLaboratoryGeneralException("Dynamic parameter instance already registered: [" + parameterName + "]"));
		}
		
		super.put(parameterName, dynamicParameter);
		logger.info("Dynamic parameter [" + parameterName + "] succesfully registered");
	}
		
	/**
	 * Easy access for the Observers that can cause reloading on a dynamic parameter 
	 * @param parameterName is the name of the parameter to be notified
	 * @param observable is the Observable that want to send notification to the dynamic parameter 
	 * 		  whose name is passed, able to behave as a an Observer
	 * @throws SimulationLaboratoryGeneralException 
	 */
	public synchronized void registerOnParameter(String parameterName, Observable observable) throws SimulationLaboratoryGeneralException {
		
		DynamicParameter dynamicParameter = getInstance().getParameter(parameterName);
		
		if(dynamicParameter == null){
		   throw (new SimulationLaboratoryGeneralException("Unable to register observer: [" + observable + "] on missing dynamic parameter: [" + parameterName + "]"));
		}
		
		if( (parameterName == null) ||
			(parameterName.trim().equals("")) ){
		   throw (new SimulationLaboratoryGeneralException("Missing dynamic parameter name: [" + parameterName + "] for observer [" + observable + "]"));
		}
		
		if(observable == null){
		   throw (new SimulationLaboratoryGeneralException("Missing observer: [" + observable + "] for dynamic parameter: [" + parameterName + "]"));
		}
		
		observable.addObserver(dynamicParameter);
		
		logger.info("Dynamic parameter: [" + parameterName + "] succesfully registered observable: [" + observable + "]");
	}
	
	/**
	 * Put in a thread and run it, the dynamic parameter instance referenced by the passed name
	 * @param parameterName is the name of the dynamic parameter to change into a thread
	 * @throws SimulationLaboratoryGeneralException 
	 */
	public synchronized void daemonizeParameter(String parameterName) throws SimulationLaboratoryGeneralException {

		DynamicParameter dynamicParameter = getInstance().getParameter(parameterName);
		
		if(dynamicParameter == null){
		   throw (new SimulationLaboratoryGeneralException("Unable to run as thread dynamic parameter with name: [" + parameterName + "]"));
		}
		
		int threadInThisThreadGroupCount = this.threadGroup.activeCount();
		Thread[] threadList = new Thread[threadInThisThreadGroupCount];
		int numberOfThreadsFoundInGroup = this.threadGroup.enumerate(threadList);
		
		if(!(numberOfThreadsFoundInGroup <= threadList.length) ){
		   throw (new SimulationLaboratoryGeneralException("Unable to get correct number of threaded parameters: [" + numberOfThreadsFoundInGroup + "] != [" + threadList.length + "]"));
		}
		
		logger.info("Threading dynamic parameter [" + parameterName + "]");
		
		//The thread name is the parameter name
		String tmpThreadName = null;
		boolean matchFound = false;
		for(Thread tmpThread: threadList){
			tmpThreadName = tmpThread.getName();
			if(tmpThreadName.equals(parameterName)){
				matchFound = true;
				break;
			}
		}
		
		if(matchFound){
		   throw (new SimulationLaboratoryGeneralException("Unable to run as thread dynamic parameter with name: [" + parameterName + "], thread already exists"));	
		}
		
		Thread thread = this.newThread(dynamicParameter, parameterName);
		
		thread.start();
		logger.info("Dynamic parameter [" + parameterName + "] thread created: [" + thread.toString() + "]");
		
	}

	/**
	 * Get the names of parameters registered
	 * @return the list of names of parameters
	 */
	public Enumeration<String> getParameterNames(){
		return this.keys();
	}
	
	/**
	 * Create a new thread with the given name in the registry internal thread group
	 * @param runnable is the worked to put in a new thread
	 * @param parameterName is the name of the parameter to be used also as thread name
	 * @return a new thread instance for the given runnable
	 */
	private Thread newThread(Runnable runnable, String parameterName) {
		Thread thread = new Thread(this.threadGroup, runnable, parameterName);
		return thread;
	}
}
